package com.example.roundcornerprogressbar.view.progressbar.common;

import com.example.roundcornerprogressbar.ResourceTable;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.app.Context;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.utils.Parcel;
import ohos.utils.Sequenceable;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;

import static ohos.agp.components.AttrHelper.vp2px;

public abstract class BaseRoundCornerProgressBar extends DirectionalLayout {

    protected final static int DEFAULT_MAX_PROGRESS = 100;
    protected final static int DEFAULT_PROGRESS = 0;
    protected final static int DEFAULT_SECONDARY_PROGRESS = 0;
    protected final static int DEFAULT_PROGRESS_RADIUS = 30;
    protected final static int DEFAULT_BACKGROUND_PADDING = 0;

    protected DirectionalLayout layoutBackground;
    protected DirectionalLayout layoutProgress;
    protected DirectionalLayout layoutSecondaryProgress;

    protected ShapeElement progressDrawable;
    protected ShapeElement secondaryProgressDrawable;

    protected int radius;
    protected int basePadding;
    protected int totalWidth;

    protected float max;
    protected float progress;
    protected float secondaryProgress;

    protected int backgroundColor;
    private int progressColor;
    protected int secondaryProgressColor;
    protected RgbColor[] progressColors;
    protected RgbColor[] secondaryProgressColors;

    protected boolean isReverse;

    protected boolean isFirstRefresh;

    protected OnProgressChangedListener progressChangedListener;

    public BaseRoundCornerProgressBar(Context context) {
        super(context);
        setup(context, null);
    }

    public BaseRoundCornerProgressBar(Context context, AttrSet attrSet) {
        super(context, attrSet);
        setup(context, attrSet);
    }

    public BaseRoundCornerProgressBar(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        setup(context, attrSet);
    }

    // Setup a progress bar layout by sub class
    protected abstract int initLayout();

    // Setup an attribute parameter on sub class
    protected abstract void initStyleable(Context context, AttrSet attrSet);

    // Initial any view on sub class
    protected abstract void initView();

    // Draw a progress by sub class
    protected abstract void drawProgress(DirectionalLayout layoutProgress,
                                         ShapeElement progressDrawable,
                                         float max,
                                         float progress,
                                         float totalWidth,
                                         int radius,
                                         int padding,
                                         boolean isReverse);

    // Draw all view on sub class
    protected abstract void onViewDraw();

    private void onRedraw(Context context, Component component) {
        totalWidth = component.getWidth();
        drawBackgroundProgress();
        drawPadding();
        drawProgressReverse();
        // Can't instantly change the size of child views (primary & secondary progress)
        // when `onSizeChanged(...)` called. Using `post` method then
        // call these methods inside the Runnable will solved this.
        // And I can't reuse the `drawAll()` method because this problem.

        TaskDispatcher dispatcher = context.getUITaskDispatcher();
        dispatcher.asyncDispatch(new Runnable() {
            @Override
            public void run() {
                isFirstRefresh = true;
                drawPrimaryProgress();
                drawSecondaryProgress();
            }
        });
        onViewDraw();
    }

    public void setup(Context context, AttrSet attrs) {
        setupStyleable(context, attrs);

        removeAllComponents();
        // Setup layout for sub class
        LayoutScatter.getInstance(context).parse(initLayout(), this, true);

        // Initial default view
        layoutBackground = (DirectionalLayout) findComponentById(ResourceTable.Id_layout_background);
        layoutProgress = (DirectionalLayout) findComponentById(ResourceTable.Id_layout_progress);
        layoutSecondaryProgress = (DirectionalLayout) findComponentById(ResourceTable.Id_layout_secondary_progress);

        initView();

        setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                if (isFirstRefresh == false) {
                    onRedraw(context, component);
                }
            }
        });
    }

    // Retrieve initial parameter from view attribute
    public void setupStyleable(Context context, AttrSet attrs) {
        radius = vp2px(DEFAULT_PROGRESS_RADIUS, context);
        basePadding = vp2px(DEFAULT_BACKGROUND_PADDING, context);

        isReverse = false;

        max = DEFAULT_MAX_PROGRESS;
        progress = DEFAULT_PROGRESS;
        secondaryProgress = DEFAULT_SECONDARY_PROGRESS;

        try {
            backgroundColor = getResourceManager().getElement(ResourceTable.Color_round_corner_progress_bar_background_default).getColor();
            progressColor = getResourceManager().getElement(ResourceTable.Color_round_corner_progress_bar_progress_default).getColor();
            secondaryProgressColor = getResourceManager().getElement(ResourceTable.Color_round_corner_progress_bar_secondary_progress_default).getColor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NotExistException e) {
            e.printStackTrace();
        } catch (WrongTypeException e) {
            e.printStackTrace();
        }
        progressColors = null;
        secondaryProgressColors = null;

        isFirstRefresh = false;

        updateProgressDrawable();
        updateSecondaryProgressDrawable();
        initStyleable(context, attrs);
    }

    @Deprecated
    protected void onSizeChanged(int newWidth,
                                 int newHeight,
                                 int oldWidth,
                                 int oldHeight) {
        //TODO 未找到对应api
    }

    @Override
    public void postLayout() {
        super.postLayout();
    }

    // Redraw all view
    protected void drawAll() {
        drawBackgroundProgress();
        drawPadding();
        drawProgressReverse();
        drawSecondaryProgress();
        drawPrimaryProgress();
        onViewDraw();
    }

    // Draw progress background
    private void drawBackgroundProgress() {
        ShapeElement backgroundDrawable = createGradientDrawable(RgbColor.fromArgbInt(backgroundColor));
        int newRadius = radius - (basePadding / 2);
        backgroundDrawable.setCornerRadiiArray(new float[]{newRadius, newRadius, newRadius, newRadius, newRadius, newRadius, newRadius, newRadius});
        layoutBackground.setBackground(backgroundDrawable);
    }

    // Create an color rectangle gradient drawable
    protected ShapeElement createGradientDrawable(RgbColor color) {
        ShapeElement gradientDrawable = new ShapeElement();
        gradientDrawable.setShape(ShapeElement.RECTANGLE);
        gradientDrawable.setRgbColor(color);
        return gradientDrawable;
    }

    // Create an multi-color rectangle gradient drawable
    protected ShapeElement createGradientDrawable(RgbColor[] colors) {
        ShapeElement gradientDrawable = new ShapeElement();
        gradientDrawable.setShape(ShapeElement.RECTANGLE);
        gradientDrawable.setOrientation(!isReverse() ? ShapeElement.Orientation.LEFT_TO_RIGHT : ShapeElement.Orientation.RIGHT_TO_LEFT);
        gradientDrawable.setRgbColors(colors);
        return gradientDrawable;
    }

    // Create gradient drawable depends on progressColor and progressColors value
    private void updateProgressDrawable() {
        if (progressColor != -1) {
            progressDrawable = createGradientDrawable(RgbColor.fromArgbInt(progressColor));
        } else if (progressColors != null && progressColors.length > 0) {
            progressDrawable = createGradientDrawable(progressColors);
        } else {
            try {
                int defaultColor = getResourceManager().getElement(ResourceTable.Color_round_corner_progress_bar_progress_default).getColor();
                progressDrawable = createGradientDrawable(RgbColor.fromArgbInt(defaultColor));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // Create gradient drawable depends on secondaryProgressColor and secondaryProgressColors value
    private void updateSecondaryProgressDrawable() {
        if (secondaryProgressColor != -1) {
            secondaryProgressDrawable = createGradientDrawable(RgbColor.fromArgbInt(secondaryProgressColor));
        } else if (secondaryProgressColors != null && secondaryProgressColors.length > 0) {
            secondaryProgressDrawable = createGradientDrawable(secondaryProgressColors);
        } else {
            try {
                int defaultColor = getResourceManager().getElement(ResourceTable.Color_round_corner_progress_bar_secondary_progress_default).getColor();
                secondaryProgressDrawable = createGradientDrawable(RgbColor.fromArgbInt(defaultColor));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void drawPrimaryProgress() {
        int possibleRadius = Math.min(radius, layoutBackground.getHeight() / 2);
        drawProgress(layoutProgress, progressDrawable, max, progress, totalWidth, possibleRadius, basePadding, isReverse);
    }

    private void drawSecondaryProgress() {
        int possibleRadius = Math.min(radius, layoutBackground.getHeight() / 2);
        drawProgress(layoutSecondaryProgress, secondaryProgressDrawable, max, secondaryProgress, totalWidth, possibleRadius, basePadding, isReverse);
    }

    private void drawProgressReverse() {
        setupProgressReversing(layoutProgress, isReverse);
        setupProgressReversing(layoutSecondaryProgress, isReverse);
    }

    // Change progress position by depending on isReverse flag
    private void setupProgressReversing(DirectionalLayout layoutProgress, boolean isReverse) {
        DependentLayout.LayoutConfig progressParams = (DependentLayout.LayoutConfig) layoutProgress.getLayoutConfig();
        removeLayoutParamsRule(progressParams);
        if (isReverse) {
            progressParams.addRule(DependentLayout.LayoutConfig.ALIGN_PARENT_RIGHT);
            progressParams.addRule(DependentLayout.LayoutConfig.ALIGN_PARENT_END);
        } else {
            progressParams.addRule(DependentLayout.LayoutConfig.ALIGN_PARENT_LEFT);
            progressParams.addRule(DependentLayout.LayoutConfig.ALIGN_PARENT_START);
        }
        layoutProgress.setLayoutConfig(progressParams);
    }

    private void drawPadding() {
        layoutBackground.setPadding(basePadding, basePadding, basePadding, basePadding);
    }

    // Remove all of relative align rule
    private void removeLayoutParamsRule(DependentLayout.LayoutConfig layoutParams) {
        layoutParams.removeRule(DependentLayout.LayoutConfig.ALIGN_PARENT_RIGHT);
        layoutParams.removeRule(DependentLayout.LayoutConfig.ALIGN_PARENT_END);
        layoutParams.removeRule(DependentLayout.LayoutConfig.ALIGN_PARENT_LEFT);
        layoutParams.removeRule(DependentLayout.LayoutConfig.ALIGN_PARENT_START);
    }

    public boolean isReverse() {
        return isReverse;
    }

    public void setReverse(boolean isReverse) {
        this.isReverse = isReverse;
        drawProgressReverse();
        drawPrimaryProgress();
        drawSecondaryProgress();
    }

    public int getRadius() {
        return radius;
    }

    /**
     * @param radius px
     */
    public void setRadius(int radius) {
        if (radius >= 0) {
            this.radius = radius;
        }
        drawBackgroundProgress();
        drawPrimaryProgress();
        drawSecondaryProgress();
    }


    public int getBasePadding() {
        return basePadding;
    }

    /**
     * @param basePadding px
     */
    public void setBasePadding(int basePadding) {
        if (basePadding >= 0) {
            this.basePadding = basePadding;
        }
        drawPadding();
        drawPrimaryProgress();
        drawSecondaryProgress();
    }

    public float getMax() {
        return max;
    }

    public void setMax(float max) {
        if (max >= 0) {
            this.max = max;
        }
        if (this.progress > max) {
            this.progress = max;
        }
        drawPrimaryProgress();
        drawSecondaryProgress();
    }

    public float getLayoutWidth() {
        return totalWidth;
    }

    public float getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        setProgress((float) progress);
    }

    public void setProgress(float progress) {
        if (progress < 0) {
            this.progress = 0;
        } else {
            this.progress = Math.min(progress, max);
        }
        drawPrimaryProgress();
        if (progressChangedListener != null) {
            progressChangedListener.onProgressChanged(this, this.progress, true, false);
        }
    }

    public float getSecondaryProgressWidth() {
        if (layoutSecondaryProgress != null) {
            return layoutSecondaryProgress.getWidth();
        }
        return 0;
    }

    public float getSecondaryProgress() {
        return secondaryProgress;
    }

    public void setSecondaryProgress(int progress) {
        setSecondaryProgress((float) progress);
    }

    public void setSecondaryProgress(float progress) {
        if (progress < 0) {
            this.secondaryProgress = 0;
        } else {
            this.secondaryProgress = Math.min(progress, max);
        }
        drawSecondaryProgress();
        if (progressChangedListener != null) {
            progressChangedListener.onProgressChanged(this, this.secondaryProgress, false, true);
        }
    }

    public int getProgressBackgroundColor() {
        return backgroundColor;
    }

    public void setProgressBackgroundColor(int color) {
        this.backgroundColor = color;
        drawBackgroundProgress();
    }

    public int getProgressColor() {
        return progressColor;
    }

    public void setProgressColor(int color) {
        this.progressColor = color;
        this.progressColors = null;
        updateProgressDrawable();
        drawPrimaryProgress();
    }

    @Nullable
    public RgbColor[] getProgressColors() {
        return progressColors;
    }

    public void setProgressColors(RgbColor[] colors) {
        this.progressColor = -1;
        this.progressColors = colors;
        updateProgressDrawable();
        drawPrimaryProgress();
    }

    public int getSecondaryProgressColor() {
        return secondaryProgressColor;
    }

    public void setSecondaryProgressColor(int color) {
        this.secondaryProgressColor = color;
        this.secondaryProgressColors = null;
        updateSecondaryProgressDrawable();
        drawSecondaryProgress();
    }

    @Nullable
    public RgbColor[] getSecondaryProgressColors() {
        return secondaryProgressColors;
    }

    public void setSecondaryProgressColors(RgbColor[] colors) {
        this.secondaryProgressColor = -1;
        this.secondaryProgressColors = colors;
        updateSecondaryProgressDrawable();
        drawSecondaryProgress();
    }

    public void setOnProgressChangedListener(@Nullable  OnProgressChangedListener listener) {
        progressChangedListener = listener;
    }

    @Override
    public void invalidate() {
        super.invalidate();
        drawAll();
    }
    /*
    TODO 未找到对应api
    protected Parcelable onSaveInstanceState() {

    }
    */

    /*
    TODO 未找到对应api
    @Override
    protected void onRestoreInstanceState(Parcelable state) {

    }
    */
    public interface OnProgressChangedListener {
        void onProgressChanged(Component view, float progress, boolean isPrimaryProgress, boolean isSecondaryProgress);
    }

    protected static class SavedState implements Sequenceable {
        float max;
        float progress;
        float secondaryProgress;

        int radius;
        int padding;

        int colorBackground;
        int colorProgress;
        int colorSecondaryProgress;
        int[] colorProgressArray;
        int[] colorSecondaryProgressArray;

        boolean isReverse;

        public boolean marshalling(Parcel out) {
            return out.writeFloat(this.max) &&
                    out.writeFloat(this.progress) &&
                    out.writeFloat(this.secondaryProgress) &&
                    out.writeInt(this.radius) &&
                    out.writeInt(this.padding) &&
                    out.writeInt(this.colorBackground) &&
                    out.writeInt(this.colorProgress) &&
                    out.writeInt(this.colorSecondaryProgress) &&
                    out.writeInt(this.colorProgressArray != null ? this.colorProgressArray.length : 0) &&
                    out.writeIntArray(this.colorProgressArray != null ? this.colorProgressArray : new int[0]) &&
                    out.writeInt(this.colorSecondaryProgressArray != null ? this.colorSecondaryProgressArray.length : 0) &&
                    out.writeIntArray(this.colorSecondaryProgressArray != null ? this.colorSecondaryProgressArray : new int[0]) &&
                    out.writeByte((byte) (this.isReverse ? 1 : 0));
        }

        @Override
        public boolean unmarshalling(Parcel in) {
            this.max = in.readFloat();
            this.progress = in.readFloat();
            this.secondaryProgress = in.readFloat();

            this.radius = in.readInt();
            this.padding = in.readInt();

            this.colorBackground = in.readInt();
            this.colorProgress = in.readInt();
            this.colorSecondaryProgress = in.readInt();
            this.colorProgressArray = new int[in.readInt()];
            in.readIntArray(this.colorProgressArray);
            this.colorSecondaryProgressArray = new int[in.readInt()];
            in.readIntArray(this.colorSecondaryProgressArray);

            this.isReverse = in.readByte() != 0;
            return true;
        }

        public static final Sequenceable.Producer PRODUCER = new Sequenceable.Producer() {

            @Override
            public SavedState createFromParcel(Parcel in) {
                SavedState instance = new SavedState();
                instance.unmarshalling(in);
                return instance;
            }
        };
    }
}
